'''
Super Famicom ROM Object
A part of the libPirate (Python) library

Author:    Derrick Sobodash <derrick@cinnamonpirate.com>
Copyright: (c) 2006, 2007, 2008 Derrick Sobodash
Web site:  http://libpirate.org/
License:   BSD License <http://opensource.org/licenses/bsd-license.php>
'''

try:
	import re
	import pirstring
	from struct import *
	from time import mktime
	import sys

except ImportError, err:
	print "Could not load %s module." % (err)
	raise SystemExit

'''
object RomSfam(data)

An object representation of a Super Famicom/Super Nintendo ROM image.
	
The object contains a Super Famicom ROM image with member variables and
methods to store and edit the game's internal header.

Inherits the libPirate File Stream functions.
'''
class RomSfam(object):
	## Contains the header of the ROM if present.
	_header = None
	
	## Contains an tuple of all cart types
	_cart = (
			"ROM Only",                     "ROM + RAM",
			"ROM + RAM + Battery",          "ROM + Co-processor",
			"ROM + Co-processor + RAM",     "ROM + Co-processor + RAM + Battery",
			"ROM + Co-processor + Battery"
		)
	
	## Contains a tuple of all BSX ROM types
	_bsxcart = (
			"FullSize ROM and SoundLink",   "FullSize ROM",
			"PartSize ROM and SoundLink",   "PartSize ROM"
		 )
	
	## Contains a tuple of all coprocessors
	_coproc = (
			"DSP",                          "Super FX",
			"OBC1",                         "SA-1",
			"S-DD1",                        "RTC",
			"Unknown[6]",                   "Unknown[7]",
			"Unknown[8]",                   "Unknown[9]",
			"Unknown[10]",                  "Unknown[11]",
			"Unknown[12]",                  "Unknown[13]",
			"Other",                        "Custom"
		)
	
	## Contains a tuple of all regions
	_region = (
			"Japan",                        "North America",
			"Europe",                       "Scandinavia",
			"France",                       "Holland",
			"Spain",                        "Germany",
			"Italy",                        "China",
			"Korea",                        "Common",
			"Canada",                       "Brazil",
			"Australia",                    "Other",
			"Other",                        "Other"
		)
	
	## Contains a tuple of all known Nintendo licensees.
	_maker = ("Invalid[00]",
			'Nintendo',               'Ajinomoto',                'Imagineer-Zoom',
			'Electronic Arts',        'Zamuse',                   'Falcom',
			'Infogrames',             'Capcom',                   'HOT B Co. Ltd',
			'Jaleco',                 'Coconuts',                 'Coconuts Japan',
			'Micronet',               'Technos',                  'Mebio Software',
			'Shouei'     ,            'Starfish',                 'Gremlin Graphics',
			'Electronic Arts',        'NCS',                      'Cobra Team',
			'Human/Field',            'KOEI',                     'Hudson Soft',
			'Sheisha/Kousaka Koji',   'Yanoman/Max Entertainment','Unknown[27]',
			'Tecmo',                  'Nippon Clary Business',    'Open System',
			'Virgin Games',           'KSS/Rumic Soft',           'Sunsoft',
			'POW',                    'Micro World',              'Bullet-Proof Software',
			'San-X',                  'Enix',                     'Loriciel/Electro Brain',
			'Kemco/Kotobuki System',  'Seta',                     'Culture Brain',
			'Irem',                   'Sotsu Agency',             'Visit',
			'INTEC',                  'System Sacom',             'Viacom International',
			'Carrozzeria',            'Dynamic',                  'Unknown[51]',
			'Magifact',               'Hect',                     'Unknown[54]',
			'Unknown[55]',            'Capcom/Laguna Games',      'Nintendo',
			'Unknown[58]',            'Arcade Zone',              'Empire Software',
			'Loriciel',               'Gremlin Graphics',         'Unknown[63]',
			'Seika',                  'UBI Soft',                 'Unknown[66]',
			'Unknown[67]',            'Life Fitness',             'Gremlin',
			'System 3',               'Spectrum Holobyte',        'Unknown[72]',
			'Irem',                   'Unknown[74]',              'Raya Systems',
			'Renovation Products',    'Malibu Games',             'Unknown[78]',
			'U.S. Gold',              'Absolute Entertainment',   'Acclaim',
			'Activision',             'American Sammy',           'GameTek',
			'Hi-Tech Expressions',    'LJN Toys',                 'Matchbox International',
			'Mattel',                 'Unknown[89]',              'Mindscape',
			'Romstar',                'Imagineer',                'Tradewest/Williams Entertainment',
			'Unknown[94]',            'American Softworks Corp.', 'Titus',
			'Virgin Games',           'Maxis',                    'Origin',
			'Unknown[100]',           'Playmates Entertainment',  'Unknown[102]',
			'Ocean',                  'Unknown[104]',             'Electronic Arts',
			'Unknown[106]',           'Laser Beam',               'Unknown[108]',
			'Unknown[109]',           'Elite',                    'Electro Brain',
			'Infogrames',             'Interplay',                'JVC/LucasArts',
			'Parker Brothers.',       'Konami',                   'Storm/The Sales Curve',
			'Unknown[118]',           'Unknown[119]',             'T*HQ Software',
			'Accolade',               'Triffix Entertainment',    'Unknown[123]',
			'Microprose',             'Unknown[125]',             'Unknown[126]',
			'Kemco',                  'Misawa Entertainment',     'Teichiku Co. Ltd.',
			'Namco',                  'Lozc',                     'KOEI',
			'Unknown[133]',           'Tokuma Shoten Intermedia', 'Tsukuda Original',
			'DATAM-Polystar',         'Unknown[137]',             'Unknown[138]',
			'Bullet-Proof Software',  'Vic Tokai',                'Unknown[141]',
			'Character Soft',         "I'Max",                    'Takara',
			'Chun Soft',              'Video System',             'BEC',
			'Unknown[148]',           'Varie',                    "Yonezawa/S'Pal",
			'Kaneco',                 'Unknown[152]',             'Pack-In-Video',
			'Nichibutsu',             'Tecmo',                    'Imagineer',
			'Unknown[157]',           'Unknown[158]',             'Nova Co.,Ltd',
			'Telenet',                'Hori Electric',            'Unknown[162]',
			'TGL',                    'Konami',                   'K.Amusement Leasing Co.',
			'Kawada',                 'Takara',                   'Unknown[168]',
			'Technos Japan',          'JVC/LucasArts',            'Unknown[171]',
			'TOEI Animation',         'Toho',                     'Unknown[174]',
			'Namco',                  'Probe Entertainment',      'ASCII Co.',
			'Bandai',                 'Pioneer LDC',              'Enix',
			'Unknown[181]',           'Halken',                   'SNK',
			'Unknown[184]',           'Pony Canyon',              'Culture Brain',
			'Sunsoft',                'Toshiba EMI',              'Sony Imagesoft',
			'Unknown[190]',           'Sammy',                    'Taito',
			'Unknown[193]',           'Kemco',                    'SquareSoft',
			'Tokuma Soft',            'Data East',                'Tonkin House',
			'Unknown[199]',           'KOEI',                     'UPL Co.,Ltd.',
			'Konami',                 'NTVIC/VAP',                'USE Co.,Ltd.',
			'Meldac',                 'Pony Canyon',              'Sotsu Agency/Sunrise',
			'Disco/Taito',            'Sofel',                    'Quest',
			'Sigma',                  'ASK Kodansha',             'Unknown[213]',
			'Naxat',                  'Copia System',             'Capcom',
			'Banpresto',              'Tomy',                     'Acclaim',
			'Unknown[220]',           'NCS',                      'Human',
			'Altron',                 'Jaleco',                   'Gaps Inc.',
			'Yutaka',                 'Varie',                    'T&ESoft',
			'Epoch',                  'Unknown[230]',             'Athena',
			'Asmik',                  'Natsume',                  'King Records',
			'Atlus',                  'Sony Music Entertainment', 'Unknown[237]',
			'IGS',                    'Unknown[239]',             'A Wave, Inc',
			'Motown Software',        'Left Field Entertainment', 'Beam Software',
			'Tec Magik',              'Unknown[245]',             'Unknown[246]',
			'Unknown[247]',           'YuMedia',                  'Cybersoft',
			'Unknown[250]',           'Psygnosis',                'Unknown[252]',
			'Unknown[253]',           'Davidson',                 'Hudson Soft'
		)
	
	## Contains the interleave map for 20mbit interleaved images.
	_intmap20m = (
			  1,  3,  5,  7,  9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 
			 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 
			 65, 67, 69, 71, 73, 75, 77, 79, 64, 66, 68, 70, 72, 74, 76, 78,
			 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62,
			  0,  2,  4,  6,  8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30
		)
	
	## Contains the interleave map for 24mbit interleaved images.
	_intmap24m = (
			  1,  3,  5,  7,  9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31,
			 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63,
			 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95,
			 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94,
			  0,  2,  4,  6,  8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30,
			 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62
		)
	
	## Contains the interleave map for 48mbit interleaved images.
	_intmap48m = (
			129,131,133,135,137,139,141,143,145,147,149,151,153,155,157,159,
			161,163,165,167,169,171,173,175,177,179,181,183,185,187,189,191,
			128,130,132,134,136,138,140,142,144,146,148,150,152,154,156,158,
			160,162,164,166,168,170,172,174,176,178,180,182,184,186,188,190,
			  1,  3,  5,  7,  9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31,
			 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63,
			 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95,
			 97, 99,101,103,105,107,109,111,113,115,117,119,121,123,125,127,
			  0,  2,  4,  6,  8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30,
			 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62,
			 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94,
			 96, 98,100,102,104,106,108,110,112,114,116,118,120,122,124,126
		)
	
	'''
	object RomSfam(data)
	
	Initialize the object from given data. Returns False if data is not
	found to be a Super Famicom binary.
	'''
	def __init__(self, data):
		
		## Check for a header. If present, remove and store it.
		if len(data) % 0x400 == 0x200:
			self._header = data[0:0x200]
			data = data[0x200: ]
		
		## Check if the header is from a UFO format ROM. UFO does not 
		## use table-based interleaves.
		if self._header != None and self._header[0x8:8] == "SUPERUFO":
			isufo = True
		else:
			isufo = False
		'''
		## Grab some places the checksum might be at
		map20_mod = ord(data[0x7fd5])
		map20_chk = unpack("<H", data[0x7fdc:0x7fde])
		map20_inv = unpack("<H", data[0x7fde:0x7fe0])
		map21_mod = ord(data[0xffd5])
		map21_chk = unpack("<H", data[0xffdc:0xffde])
		map21_inv = unpack("<H", data[0xffde:0xffe0])
		
		## Check for Map Mode 20 (no interleave)
		if map20_chk[0] + map20_inv[0] == 0xffff and map20_mod % 0x10 == 0:
			self._data = data
			self._infoblock = 0x7fb0
		
		## Check for Map Modes 21, 23, 25 (no interleave)
		elif map21_chk[0] + map21_inv[0] == 0xffff and map21_mod % 0x10 != 0:
			self._data = data
			self._infoblock = 0xffb0
		
		## Check for Map Modes 21, 23, 25 (interleaved)
		elif map20_chk[0] + map20_inv[0] == 0xffff and map20_mod % 0x10 != 0:
			print "Panic"

			
			## Table-based deinterleave methods
			if (len(data) == 0x280000 or len(data) == 0x300000 or len(data) == 0x600000) and isufo == False:
				chunks = []
				if len(data) == 0x280000:
					table = self._intmap20m
				elif len(data) == 0x300000:
					table = self._intmap24m
				elif len(data) == 0x600000:
					table = self._intmap48m
				for i in range(0, len(data)/0x8000):
					chunks.append(data[i*0x8000:i*0x8000+0x8000])
				deinterleave = ""
				for i in range(0, len(data)/0x8000):
					deinterleave += chunks[table.index(i)]
				self._data = data
				self._infoblock = 0xffb0
			
			## Standard deinterleave method
			else:
				chunks = len(data) / (0x8000)
				deinterleave = ""
				for i in range(0, chunks/2):
					deinterleave += data[(i+(chunks/2))*0x8000:(i+(chunks/2))*0x8000+0x8000]
					deinterleave += data[i*0x8000:i*0x8000+0x8000]
				self._data = data
				self._infoblock = 0xffb0
		
		## Panic! Something is royally fucked!
		elif (map20_mod % 0x10 == 0 or map21_mod % 0x10 != 0) and data[0xffb6:0xffbc] == "\0\0\0\0\0\0":
			self._data = data
			self._infoblock = 0xffb0
		else:
			sys.exit()
		'''
		score_lo = self.score_header(data, 0x007fc0)
		score_hi = self.score_header(data, 0x00ffc0)
		score_ex = self.score_header(data, 0x40ffc0)
		
		## Catch BS-X ROMs which lack a reset vector and break the test
		if score_lo == 0 and score_hi == 0 and score_ex == 0:
			score_lo = self.score_bsx(data, 0x007fc0)
			score_hi = self.score_bsx(data, 0x00ffc0)

		## Favor ExHiROM on images over 32 megabits
		if score_ex > 0:
			score_ex += 4

		if score_lo >= score_hi and score_lo >= score_ex:
			self._data      = data
			self._infoblock = 0x007fb0

		elif score_hi >= score_ex:
			self._data      = data
			self._infoblock = 0x00ffb0

		else:
			self._data      = data
			self._infoblock = 0x40ffb0

	'''
	int score_header()

	Ported from the bsnes source code. This function analyzes the binary to
	identify the most likely location of the internal header.
	'''
	def score_header(self, data, addr):
		## Make sure binary is long enough to even have a header here
		if len(data) < addr + 64:
			return(0)
		score = 0

		## Fetch data
		resetvector = unpack("<H", data[addr + 0x3c:addr + 0x3e])
		checksum    = unpack("<H", data[addr + 0x1e:addr + 0x20])
		complement  = unpack("<H", data[addr + 0x1c:addr + 0x1e])
		mapper      = ord(data[addr + 0x15]) & ~0x10

		## First opcode executed on reset
		resetop     = ord(data[(addr & ~0x7fff) | (resetvector[0] & 0x7fff)])

		## $00:[000-7fff] contains uninitialized RAM and MMIO
		## Reset vector must point to ROM at $00:[8000-ffff]
		if resetvector[0] < 0x8000:
			return(0)
		
		## Check for likely opcodes:
		## sei
		## clc (clc; xce)
		## sec (sec; xce)
		## stz $nnnn (stz $4200)
		## jmp $nnnn
		## jml $nnnnnn
		if resetop == 0x78 or resetop == 0x18 or resetop == 0x38 or resetop == 0x9c or resetop == 0x4c or resetop == 0x5c:
			score += 8
		
		## Check for plausible opcodes:
		## rep #$nn
		## sep #$nn
		## lda $nnnn
		## ldx $nnnn
		## ldy $nnnn
		## lda $nnnnnn
		## lda #$nn
		## ldx #$nn
		## ldy #$nn
		## jsr $nnnn
		## jsl $nnnnnn
		if resetop == 0xc2 or resetop == 0xe2 or resetop == 0xad or resetop == 0xae or resetop == 0xac or resetop == 0xaf or resetop == 0xa9 or resetop == 0xa2 or resetop == 0xa0 or resetop == 0x20 or resetop == 0x22:
			score += 4
		
		## Check for implausible opcodes:
		## rti
		## rts
		## rtl
		## cmp $nnnn
		## cpx $nnnn
		## cpy $nnnn
		if resetop == 0x40 or resetop == 0x60 or resetop == 0x6b or resetop == 0xcd or resetop == 0xec or resetop == 0xcc:
			score -= 4

		## Check for least likely opcodes:
		## brk #$nn
		## cop #$nn
		## stp
		## wdm
		## sbc $nnnnnn,x
		if resetop == 0x00 or resetop == 0x02 or resetop == 0xdb or resetop == 0x42 or resetop == 0xff:
			score -= 8

		## Check for a valid checksum and complement
		if (checksum[0] + complement[0]) == 0xffff and checksum != 0 and complement != 0:
			score += 4

		## Mapper 0x20 = LoROM
		if addr == 0x007fc0 and mapper == 0x20:
			score += 2

		## Mapper 0x21 == HiROM
		if addr == 0x00ffc0 and mapper == 0x21:
			score += 2

		## Mapper 0x22 == ExLoROM
		if addr == 0x007fc0 and mapper == 0x22:
			score += 2
		
		## Mapper 0x25 == ExHiROM
		if addr == 0x40ffc0 and mapper == 0x25:
			score += 2

		## Check for revised header
		if data[addr + 0x1a] == "\x33":
			score += 2

		## Check ROM type
		if ord(data[addr + 0x16]) < 0x08:
			score += 1

		## Check ROM size
		if ord(data[addr + 0x17]) < 0x10:
			score += 1
		
		## Check RAM size
		if ord(data[addr + 0x18]) < 0x08:
			score += 1

		## Check region
		if ord(data[addr + 0x19]) < 14:
			score += 1

		if score < 0:
			score = 0
		
		return(score)
	
	'''
	int score_bsx()

	When score_header fails, it is most likely because the game is booted from a
	BIOS and lacks code to reset vector.
	Assume it is a BS-X binary and score based on other header bytes.
	'''
	def score_bsx(self, data, addr):
		## Make sure binary is long enough to even have a header here
		if len(data) < addr + 64:
			return(0)
		score = 0

		## Fetch data
		checksum    = unpack("<H", data[addr + 0x1e:addr + 0x20])
		complement  = unpack("<H", data[addr + 0x1c:addr + 0x1e])
		mapper      = ord(data[addr + 0x18]) & ~0x10
		
		## Check for a valid checksum and complement
		if (checksum[0] + complement[0]) == 0xffff and checksum != 0 and complement != 0:
			score += 4

		## Try the BS-X test
		if data[addr + 0x13] == "\x00" or data[addr + 0x13] == "\xff":
			if data[addr + 0x14] == "\x00":
				n15 = data[addr + 0x15]
				if n15 == "\x00" or n15 == "\x80" or n15 == "\x84" or n15 == "\x9c" or n15 == "\xbc" or n15 == "\xfc":
					if data[addr + 0x1a] == "\x33" or data[addr + 0x1a] == "\xff":
						score += 4

		## Mapper 0x20 = LoROM
		if addr == 0x007fc0 and mapper == 0x20:
			score += 2

		## Mapper 0x21 == HiROM
		if addr == 0x00ffc0 and mapper == 0x21:
			score += 2

		## Mapper 0x22 == ExLoROM
		if addr == 0x007fc0 and mapper == 0x22:
			score += 2
		
		## Mapper 0x25 == ExHiROM
		if addr == 0x40ffc0 and mapper == 0x25:
			score += 2

		## Check for revised header
		if data[addr + 0x1a] == "\x33":
			score += 2

		## Check for valid release month
		if ord(data[addr + 0x16]) / 10 < 13 and ord(data[addr + 0x16]) % 10 == 0:
			score += 2

		## Check for valid release day
		if (ord(data[addr + 0x17]) % 10 >= 0 and ord(data[addr + 0x17]) % 10 <= 3) or (ord(data[addr + 0x17]) % 10 >= 8 and ord(data[addr + 0x17]) % 10 <= 11):
			score += 1

		## Check ROM type
		if ord(data[addr + 0x19]) / 10 < 4:
			score += 1

		if score < 0:
			score = 0
		
		return(score)
	

	'''
	bool issufami()
	
	Tests if loaded ROM is a Sufami Turbo ROM.
	'''
	def issufami(self):
		if self.getname() == "ADD-ON BASE CASSETE":
			return(True)
		else:
			return(False)
	
	'''
	bool issatella()
	
	Tests if loaded ROM is a Broadcast Satellaview ROM.
	'''
	def issatella(self):
		## Detection code taken from bsnes
		addr = self._infoblock + 0x10
		if self._data[addr + 0x13] == "\x00" or self._data[addr + 0x13] == "\xff":
			if self._data[addr + 0x14] == "\x00":
				n15 = self._data[addr + 0x15]
				if n15 == "\x00" or n15 == "\x80" or n15 == "\x84" or n15 == "\x9c" or n15 == "\xbc" or n15 == "\xfc":
					if self._data[addr + 0x1a] == "\x33" or self._data[addr + 0x1a] == "\xff":
						return(True)
		else:
			return(False)

	'''
	bool issocket()
	
	Tests if loaded ROM has a Broadcast Satellaview cart socket.
	'''
	def issocket(self):
		if self._data[self._infoblock + 0x02] == "Z" and self._data[self._infoblock + 0x05] == "J" and (self._data[self._infoblock + 0x03].isupper() or self._data[self._infoblock + 0x03].isdigit()):
			return(True)
		else:
			return(False)
	
	'''
	bool isrevised()
	
	Test if loaded ROM uses the revised Nintendo header
	'''
	def isrevised(self):
		if self._data[self._infoblock + 0x2a] == "\x33" and re.compile("[A-F0-9][A-F0-9]").match(self._data[self._infoblock:self._infoblock+2]) != None:
			return(True)
		else:
			return(False)
	
	'''
	bool issetadsp()
	
	Tests if loaded ROM uses Seta's DSP.
	'''
	def issetadsp(self):
		if self._data[self._infoblock + 0x25] == "\x30" and self._data[self._infoblock + 0x26] == "\xf6":
			return(True)
		else:
			return(False)
	
	'''
	bool issetarisc()
	
	Tests if loaded ROM uses Seta's RISC.
	'''
	def issetarisc(self):
		if self._data[self._infoblock + 0x25] == "\x30" and self._data[self._infoblock + 0x26] == "\xf5":
			return(True)
		else:
			return(False)
	
	'''
	bool isobc1()
	
	Tests if loaded ROM uses the OBC1 chip.
	'''
	def isobc1(self):
		if self._data[self._infoblock + 0x25] == "\x30" and self._data[self._infoblock + 0x26] == "\x25":
			return(True)
		else:
			return(False)
	
	'''
	bool ismariofx()
	
	Tests if loaded ROM uses the Super FX Mario Chip.
	'''
	def ismariofx(self):
		if self._data[self._infoblock + 0x25] == "\x20" and self._data[self._infoblock + 0x26] == "\x13":
			return(True)
		else:
			return(False)
	
	'''
	bool issfx()
	
	Tests if loaded ROM uses the Super FX chip.
	'''
	def issfx(self):
		# Make sure it is not Super FX2, which gives a false positive
		if self.issfx2 == True:
			return(False)
		if self._data[self._infoblock + 0x25] == "\x20" and (self._data[self._infoblock + 0x26] == "\x14" or self._data[self._infoblock + 0x26] == "\x15"):
			return(True)
		else:
			return(False)
	
	'''
	bool issfx2()
	
	Tests if loaded ROM uses the Super FX2 chip.
	'''
	def issfx2(self):
		if self._data[self._infoblock + 0x25] == "\x20" and (self._data[self._infoblock + 0x26] == "\x14" or self._data[self._infoblock + 0x26] =="\x15") and len(self._data) > 0xfffff:
			return(True)
		else:
			return(False)
	
	'''
	bool isocsfx()
	
	Tests if loaded ROM uses the overclocked (21MHz) Super FX chip.
	'''
	def isocsfx(self):
		if self._data[self._infoblock + 0x25] == "\x20" and self._data[self._infoblock + 0x26] == "\x1a":
			return(True)
		else:
			return(False)
	
	'''
	bool issa1()
	
	Tests if loaded ROM uses the SA-1 chip.
	'''
	def issa1(self):
		if self._data[self._infoblock + 0x25] == "\x23" and (self._data[self._infoblock + 0x26] == "\x34" or self._data[self._infoblock + 0x26] == "\x35"):
			return(True)
		# This is not confirmed as correct, but it fixes Pachi Slot Monogatari
		elif self.getcoproc() == "SA-1":
			return(True)
		else:
			return(False)
	
	'''
	bool isc4()
	
	Tests if loaded ROM uses the C4 chip.
	'''
	def isc4(self):
		if self._data[self._infoblock + 0x25] == "\x20" and self._data[self._infoblock + 0x26] == "\xf3":
			return(True)
		else:
			return(False)
	
	'''
	bool S-DD1()
	
	Tests if loaded ROM uses the SPC7110 chip.
	'''
	def issdd1(self):
		if self._data[self._infoblock + 0x25] == "\x32" and (self._data[self._infoblock + 0x26] == "\x43" or self._data[self._infoblock + 0x26] == "\x45"):
			return(True)
		else:
			return(False)
	
	'''
	bool isspc7110()
	
	Tests if loaded ROM uses the SPC7110 chip.
	'''
	def isspc7110(self):
		if self._data[self._infoblock + 0x25] == "\x3a" and (self._data[self._infoblock + 0x26] == "\xf5" or self._data[self._infoblock + 0x26] == "\xf9"):
			return(True)
		else:
			return(False)
	
	'''
	bool issgb()
	
	Tests if loaded ROM is a Super Gameboy BIOS chip.
	'''
	def issgb(self):
		if self._data[self._infoblock + 0x25] == "\x20" and self._data[self._infoblock + 0x26] == "\xe3":
			return(True)
		else:
			return(False)
	
	'''
	bool isrtc()
	
	Tests if loaded ROM uses a Real Time Clock (RTC).
	'''
	def isrtc(self):
		if self._data[self._infoblock + 0x25] == "\x35" and self._data[self._infoblock + 0x26] == "\x55":
			return(True)
		else:
			return(False)
	
	'''
	bool isdsp1()
	
	Tests if loaded ROM uses the DSP-1 chip.
	'''
	def isdsp1(self):
		if (self._data[self._infoblock + 0x25] == "\x20" or self._data[self._infoblock + 0x25] == "\x21") and self._data[self._infoblock + 0x26] == "\x03":
			return(True)
		elif self._data[self._infoblock + 0x25] == "\x30" and self._data[self._infoblock + 0x26] == "\x05":
			return(True)
		elif self._data[self._infoblock + 0x25] == "\x31" and (self._data[self._infoblock + 0x26] == "\x03" or self._data[self._infoblock + 0x26] == "\x05"):
			return(True)
		else:
			return(False)
	
	'''
	bool isdsp2()
	
	Tests if loaded ROM uses the DSP-2 chip.
	'''
	def isdsp2(self):
		if self._data[self._infoblock + 0x25] == "\x20" and self._data[self._infoblock + 0x26] == "\x05":
			return(True)
		else:
			return(False)
	
	'''
	bool isdsp3()
	
	Tests if loaded ROM uses the DSP-3 chip.
	'''
	def isdsp3(self):
		if self._data[self._infoblock + 0x25] == "\x30" and self._data[self._infoblock + 0x26] == "\x05" and self._data[self._infoblock + 0x2a] == "\xb2":
			return(True)
		else:
			return(False)
	
	'''
	bool isdsp4()
	
	Tests if loaded ROM uses the DSP-4 chip.
	'''
	def isdsp4(self):
		if self._data[self._infoblock + 0x25] == "\x30" and self._data[self._infoblock + 0x26] == "\x03":
			return(True)
		else:
			return(False)
	
	'''
	string getcode()
	
	Returns the ROM code if present. ROM codes are only included in ROMs
	following the revised SNES header specification.
	'''
	def getcode(self):
		if self.isrevised() == False:
			return(False)
		code = self._data[self._infoblock + 0x2:self._infoblock + 0x6]
		if re.compile("[A-Z0-9 ][A-Z0-9 ][A-Z0-9 ][A-Z0-9 ]").match(code) != None:
			return(code)
		else:
			return(False)
	
	'''
	bool setcode(code)
	
	Sets the ROM code if the ROM is following the revised SNES header
	specification.
	'''
	def setcode(self, code):
		if self.isrevised() == False:
			return(False)
		## Pad code to 4 and upper-case it
		if len(code) < 4:
			code = code.ljust(4, " ")
		code = code.upper()
		if re.compile("[A-Z0-9 ][A-Z0-9 ][A-Z0-9 ][A-Z0-9 ]").match(code) != None:
			self._data = pirstring.strsub(self._data, self._infoblock + 0x02, code[0:4])
			return(True)
		else:
			return(False)
	
	'''
	int getexram()
	
	Returns the size of expansion RAM on the cartridge if used. Only present in
	ROMs which follow the revised SNES header specification.
	'''
	def getexram(self):
		if self.isrevised() == False:
			return(False)
		size = ord(self._data[self._infoblock + 0xd])
		if size == 0:
			return(False)
		calc = 0x400 << size
		if calc < 0x2000000:
			return(calc)
		else:
			return(False)
	
	'''
	bool setexram(size)
	
	Sets the size of the expansion RAM if the ROM is following the revised SNES
	header specification.
	'''
	def setexram(self, size):
		if self.isrevised() == False:
			return(False)
		x = 0
		z = 0x400
		while size != z:
			z = z << 1
			x += 1
		self._data = pirstring.strsub(self._data, self._infoblock + 0x0d, chr(x))
		return(True)
	
	'''
	string getname()
	
	Returns the name of the ROM image.
	'''
	def getname(self):
		if self.issatella() == True:
			return(self._data[self._infoblock + 0x10:self._infoblock + 0x20].rstrip(" \x00\xff"))
		else:
			return(self._data[self._infoblock + 0x10:self._infoblock + 0x25].rstrip())
	
	'''
	bool setname(name)
	
	Sets the name of the ROM image.
	'''
	def setname(self, name):
		name = name.ljust(0x15, " ")
		if self.issatella():
			name = name[0:0x10]
		else:
			name = name[0:0x15]
		self._data = pirstring.strsub(self._data, self._infoblock + 0x10, name)
		return(True)
	
	'''
	int getspecial()
	
	Returns the ROM's special version number
	'''
	def getspecial(self):
		if self.isrevised() == False:
			return(False)
		return(ord(self._data[self._infoblock + 0x0e]));

	'''
	bool setspecial()
	
	Sets the ROM's special version number
	'''
	def setspecial(self, special):
		if self.isrevised() == False or special > 255:
			return(False)
		self._data = pirstring.strsub(self._data, self._infoblock + 0xe, chr(special))
		return(True);
	
	'''
	int getsubnum()
	
	Returns the ROM's cart type sub-number
	'''
	def getsubnum(self):
		if self.isrevised() == False:
			return(False)
		return(ord(self._data[self._infoblock + 0x0f]));
	
	'''
	int getrevision()
	
	Returns the ROM's revision number.
	'''
	def getrevision(self):
		return(ord(self._data[self._infoblock + 0x2b]));
	
	'''
	bool setrevision(number)
	
	Sets the ROM's revision number.
	'''
	def setrevision(self, number):
		if number > 255:
			return(False)
		self._data = pirstring.strsub(self._data, self._infoblock + 0x2b, chr(number))
		return(True)
	
	'''
	string getmaker()
	
	Returns a string with the ROM's maker/licensee.
	'''
	def getmaker(self):
		if self.issufami() == True:
			return("Bandai")
		code = ord(self._data[self._infoblock + 0x2a])
		if code == 0x33:
			code = self._data[self._infoblock:self._infoblock+2]
			if ord(code[0]) < 0x47 and ord(code[0]) > 0x2f and ord(code[1]) < 0x47 and ord(code[0]) > 0x2f:
				code = int(code, 16)
			else:
				bad = unpack("<H", code)
				return("Invalid(" + str(hex(bad[0])) + ")")
		return(self._maker[code]);
	
	'''
	bool setmaker(maker)
	
	Sets the ROM's maker from an input integer 0-255. Will force old ROM
	headers to the new specifications.
	'''
	def setmaker(self, maker):
		if maker > 255 or self.issufami() == True:
			return(False)
		if self.isrevised() == True:
			self._data = pirstring.strsub(self._data, self._infoblock, hex(maker)[2: ])
		else:
			self._data = pirstring.strsub(self._data, self._infoblock + 0x2a, chr(maker))
		return(True)
	
	'''
	int getmap()
	
	Returns the ROM's mapping mode (board type).
	'''
	def getmap(self):
		if self.issatella() == True:
			code = ord(self._data[self._infoblock + 0x28]) % 0x10
		else:
			code = ord(self._data[self._infoblock + 0x25]) % 0x10
		return(code + 20)
	
	'''
	int setmap()
	
	Sets the ROM's mapping mode (board type).
	'''
	def setmap(self, map):
		if self.issatella() == True:
			code = ord(self._data[self._infoblock + 0x28]) / 0x10
		else:
			code = ord(self._data[self._infoblock + 0x25]) / 0x10
		if map < 20 or map > 29:
			return(False)
		map -= 20
		if self.issatella() == True:
			self._data = pirstring.strsub(self._data, self._infoblock + 0x28, chr(code * 0x10 + map))
		else:
			self._data = pirstring.strsub(self._data, self._infoblock + 0x25, chr(code * 0x10 + map))
		return(True)
	
	'''
	string getcpu()
	
	Returns a string with the speed of the CPU in MHz.
	'''
	def getcpu(self):
		if self.issatella() == True:
			code = ord(self._data[self._infoblock + 0x28]) / 0x10
		else:
			code = ord(self._data[self._infoblock + 0x25]) / 0x10
		if code == 2:
			return("2.68 MHz")
		elif code == 3:
			return("3.58 MHz")
		else:
			return("Unknown")
	
	'''
	int setcpu()
	
	Sets the speed of the CPU. Valid values are:
	  2 - 2.68 MHz
		3 - 5.58 MHz
	'''
	def setcpu(self, cpu):
		if cpu < 2 or cpu > 3:
			return(False)
		if self.issatella() == True:
			code = ord(self._data[self._infoblock + 0x28]) % 0x10
		else:
			code = ord(self._data[self._infoblock + 0x25]) % 0x10
		if self.issatella() == True:
			self._data = pirstring.strsub(self._data, self._infoblock + 0x28, chr(cpu * 0x10 + code))
		else:
			self._data = pirstring.strsub(self._data, self._infoblock + 0x25, chr(cpu * 0x10 + code))
		return(True)
	
	'''
	string getcart()
	
	Returns a string to describe the cartridge type.
	'''
	def getcart(self):
		if self.issatella() == True:
			pos = 0x29
		else:
			pos = 0x26
		code = ord(self._data[self._infoblock + pos]) % 0x10
		if code < 7:
			if self.issatella() == True and code < 4:
				return(self._bsxcart[code])
			else:
				return(self._cart[code])
		else:
			return("Unknown[" + str(code) + "]")
	
	'''
	string getcoproc()
	
	Returns a string containing the name of the onboard co-processor
	if present.
	'''
	def getcoproc(self):
		if self.issatella() == True:
			return(False)
		if ord(self._data[self._infoblock + 0x26]) % 0x10 < 3:
			return(False)
		code = ord(self._data[self._infoblock + 0x26]) / 0x10
		return(self._coproc[code])

	'''
	int getromsize()
	
	Returns the size of the ROM in bytes
	'''
	def getromsize(self):
		if self.issatella() == True:
			return(False)
		size = ord(self._data[self._infoblock + 0x27])
		if size == 0:
			return(False)
		calc = 0x400 << size
		if calc < 0x2000000:
			return(calc)
		else:
			return(False)
	
	'''
	bool setromsize(size)
	
	Sets the size of the ROM in bytes.
	'''
	def setromsize(self, size):
		if self.issatella() == True:
			return(False)
		x = 0
		z = 0x400
		while size != z:
			z = z << 1
			x += 1
		self._data = pirstring.strsub(self._data, self._infoblock + 0x27, chr(x))
		return(True)
	
	'''
	int getramsize()
	
	Returns the size of the onboard RAM (battery) in bytes
	'''
	def getramsize(self):
		if self.issatella() == True:
			return(False)
		if self.issufami() == True or self.issatella() == True:
			return(False)
		size = ord(self._data[self._infoblock + 0x28])
		if size == 0:
			return(False)
		calc = 0x400 << size
		if calc < 0x2000000:
			return(calc)
		else:
			return(False)
	
	'''
	bool setramsize(size)
	
	Sets the size of the onboard RAM (battery) in bytes.
	'''
	def setramsize(self, size):
		if self.issatella() == True:
			return(False)
		x = 0
		z = 0x400
		while size != z:
			z = z << 1
			x += 1
		self._data = pirstring.strsub(self._data, self._infoblock + 0x28, chr(x))
		return(True)
	
	'''
	string getregion()
	
	Return a string containing the release region.
	'''
	def getregion(self):
		## Catch the Gateway System which has no true region byte
		if self.isrevised() == True:
			if self._data[self._infoblock + 0x5] == "G":
				return("Nintendo Gateway System")
		if self.issufami() == True:
			return(self._region[0])
		code = ord(self._data[self._infoblock + 0x29])
		if code > 20:
			return(False)
		return(self._region[code])	

	'''
	string setregion()
	
	Return a string containing the release region.
	'''
	def getregion(self):
		## Catch the Gateway System which has no true region byte
		if self.isrevised() == True:
			if self._data[self._infoblock + 0x5] == "G":
				return("Nintendo Gateway System")
		if self.issufami() == True:
			return(self._region[0])
		code = ord(self._data[self._infoblock + 0x29])
		if code > 20:
			return(False)
		return(self._region[code])
	
	'''
	int getdate()
	
	Returns a UNIX timestamp of the release/broadcast date of the ROM.
	Year is always set to 1995. Don't ask ...
	'''
	def getdate(self):
		if self.issatella() == False:
			return(False)
		month = ord(self._data[self._infoblock + 0x26]) / 10
		mult = ord(self._data[self._infoblock + 0x27]) % 10
		if mult < 0xc:
			day = mult * ord(self._data[self._infoblock + 0x27]) / 10
		else:
			day = 1
		return(mktime((1995, month, day, 0, 0, 0, 0, 1, -1)))
	
	'''
	int getchecksum()
	
	Returns the currently stored ROM checksum.
	'''
	def getchecksum(self):
		checksum = unpack("<H", self._data[self._infoblock + 0x2e:self._infoblock + 0x30])
		return(checksum[0])
	
	'''
	bool newchecksum()
	
	Calculate current ROM's correct checksum.
	'''
	def newchecksum(self):
		## Set the checksum to 0xffff and inverse to 0x0000
		self.setchecksum(0xffff)
		rolling_sum = 0
		dummy = 0
		last = 0

		## Time to do some calculations. We need to break the data into
		## 8Mbit sections and loop the last chunk till full.
		size = len(self._data) / 0x20000;
		
		## 10Mbit ROM
		if size == 10:
			for i in range(0, 8 * 0x20000):
				rolling_sum += ord(self._data[i])
				last = i
			for i in range(last, 8 * 0x20000):
				dummy += ord(self._data[i])
			rolling_sum += dummy * 2
		## 12Mbit ROM
		elif size == 12:
			for i in range(0, 8 * 0x20000):
				rolling_sum += ord(self._data[i])
				last = i
			for i in range(last, 12 * 0x20000):
				dummy += ord(self._data[i])
			rolling_sum += dummy * 4
		## 20Mbit ROM
		elif size == 20:
			for i in range(0, 16 * 0x20000):
				rolling_sum += ord(self._data[i])
				last = i
			for i in range(last, 20 * 0x20000):
				dummy += ord(self._data[i])
			rolling_sum += dummy * 4
		## 24Mbit ROM
		elif size == 24:
			for i in range(0, 16 * 0x20000):
				rolling_sum += ord(self._data[i])
				last = i
			for i in range(last, 24 * 0x20000):
				dummy += ord(self._data[i])
			rolling_sum += dummy * 2
		## All other sizes
		else:
			for i in range(0, size * 0x20000):
				rolling_sum += ord(self._data[i])
		
		## Calculate the checksum
		calculated = rolling_sum & 0xffff;
		self.setchecksum(calculated)
		return(True)
	
	'''
	bool setchecksum(checksum)
	
	Forcibly set a checksum--even if it is wrong.
	'''
	def setchecksum(self, checksum):
		if checksum > 0xffff:
			return(False)
		self._data = pirstring.strsub(self._data, self._infoblock + 0x2c, pack("<H", 0xffff - checksum) + pack("<H", checksum))
		return(True)


